<!--
 * @Description: 表格封装
 * @Author: HuiSir<273250950@qq.com>
 * @Date: 2021-10-28 10:25:24
 * @LastEditTime: 2025-09-16 18:28:12
-->
<template>
  <!-- 工具栏 -->
  <!-- 插槽search（搜索栏）必须在tools工具栏存在或showSearchFormInitial为true情况下才能显示 -->
  <table-tools
    v-if="toolsOptions?.tools?.length || toolsOptions?.showSearchFormInitial"
    :cols="cols"
    :el-table="tableRef"
    v-bind="toolsOptions"
    @update:cols="$emit('update:cols', $event)"
    @update:addFormOptions="updateToolsOptions({ addFormOptions: $event })"
    @update:searchFormOptions="
      updateToolsOptions({ searchFormOptions: $event })
    "
    @update:searchFormParams="updateToolsOptions({ searchFormParams: $event })"
    @searchSubmit="$emit('searchSubmit')"
    @searchReset="$emit('searchReset')"
    @btnClick="handleToolsBtnClick"
    @delete="handleToolsDelete"
    @refresh="$emit('toolsRefresh')"
    @dialogSubmit="handleToolsAddSubmit"
  >
    <!-- 搜索表单插槽 -->
    <template
      v-for="opt in searchSlotOpts"
      :key="opt.key"
      v-slot:[opt.slot]="scope"
    >
      <slot :name="opt.slot" v-bind="scope" />
    </template>
    <!-- 左侧插槽，这里也可以放搜索表单 -->
    <template v-if="!!$slots.toolsLeft" #left="scope">
      <slot name="toolsLeft" v-bind="scope" />
    </template>
    <!-- 右侧插槽 -->
    <template v-if="!!$slots.toolsRight" #right="scope">
      <slot name="toolsRight" v-bind="scope" />
    </template>
    <!-- 编辑弹窗表单插槽 -->
    <template
      v-for="item in toolsAddDialogSlotOptions"
      :key="item.key"
      v-slot:[item.slot]="scope"
    >
      <slot :name="item.slot" v-bind="scope" />
    </template>
  </table-tools>
  <!-- 使用`v-bind="$attrs"`可继承组件调用是所配置的attr,这里可继承el-table组件所需要的所有属性及事件以及其他未作为props的行内属性 -->
  <!-- 继承的属性配置详见文档：https://element-plus.gitee.io/zh-CN/component/table.html#table-attributes -->
  <!-- 继承的事件文档：https://element-plus.gitee.io/zh-CN/component/table.html#table-events -->
  <div ref="tableBox" :style="{ height: height }">
    <el-table
      ref="myTable"
      v-bind="$attrs"
      v-loading="loading"
      size="small"
      :data="filterData"
      :border="border"
      :max-height="tableMaxHeight"
      @selection-change="handleSelectionChange"
      @filter-change="handleFilterChange"
    >
      <el-table-column
        v-for="col in cols.filter((col) => !col.hide)"
        :key="col.prop || col.slot"
        v-bind="col"
        :column-key="col.prop || col.slot || col.slotHead"
      >
        <!-- 模糊搜索 -->
        <template v-if="col.useFuzzySearch" #header>
          <fy-fuzzy-search
            v-model="filterData"
            :copyData="copyData"
            :placeholder="col.fuzzySearchPlaceholder"
          />
        </template>
        <!-- 表头插槽 -->
        <template v-else-if="col.slotHead" #header>
          <slot :name="col.slotHead" />
        </template>
        <!-- 表头-带提示插槽 -->
        <template v-else-if="col.helpTip" #header>
          <div class="header-tip">
            <span>{{ col.label }}</span>
            <el-tooltip effect="light" :content="col.helpTip" placement="top">
              <fy-icon
                name="question-filled"
                size="14"
                color="var(--el-color-info)"
                style="margin-left: 2px"
                :height="18"
              />
            </el-tooltip>
          </div>
        </template>
        <!-- 使用内置行按钮插槽 -->
        <template v-if="col.useRowBtns" #default="scope">
          <fy-row-btns
            :contains="
              typeof col.useRowBtns == 'boolean' ? undefined : col.useRowBtns
            "
            :rowData="scope.row"
            v-bind="rowBtnsOptions"
            @update:detailOptions="updateBtnsOptions({ detailOptions: $event })"
            @update:addFormOptions="
              updateBtnsOptions({ addFormOptions: $event })
            "
            @update:editFormOptions="
              updateBtnsOptions({ editFormOptions: $event })
            "
            @submit="rowBtnsSubmit"
            @delete="rowBtnsDelete"
          >
            <!-- 默认插槽 -->
            <template #default>
              <slot name="rowBtns" :row="scope.row" :table="tableRef" />
            </template>
            <!-- 左侧插槽 -->
            <template #left>
              <slot name="rowBtnsLeft" :row="scope.row" :table="tableRef" />
            </template>
            <!-- 右侧插槽 -->
            <template #right>
              <slot name="rowBtnsRight" :row="scope.row" :table="tableRef" />
            </template>
            <!-- 编辑弹窗表单插槽 -->
            <template
              v-for="item in rowBtnsEditDialogSlotOptions"
              :key="item.key"
              v-slot:[item.slot]="scope"
            >
              <slot :name="item.slot" v-bind="scope" />
            </template>
            <!-- 新增弹窗表单插槽 -->
            <template
              v-for="item in rowBtnsAddDialogSlotOptions"
              :key="item.key"
              v-slot:[item.slot]="scope"
            >
              <slot :name="item.slot" v-bind="scope" />
            </template>
            <!-- 详情弹窗插槽 -->
            <template
              v-for="item in rowBtnsDetailDialogSlotOptions"
              :key="item.key"
              v-slot:[item.slot]="scope"
            >
              <slot :name="item.slot" v-bind="scope" />
            </template>
          </fy-row-btns>
        </template>
        <!-- 单元格插槽 -->
        <template v-else-if="col.slot" #default="scope">
          <slot :name="col.slot" v-bind="scope" />
        </template>
      </el-table-column>
    </el-table>
    <el-pagination
      v-if="page"
      size="small"
      class="table-page-nav"
      :currentPage="curr"
      :page-sizes="limits"
      :page-size="limit"
      layout="total, sizes, prev, pager, next, jumper"
      :total="total"
      background
      hide-on-single-page
      @size-change="handleSizeChange"
      @current-change="handleCurrChange"
    >
    </el-pagination>
  </div>
</template>

<script lang="ts">
/* eslint-disable @typescript-eslint/no-explicit-any */
import { computed, defineComponent, PropType, Ref, ref, watch } from "vue";
import TableTools from "./TableTools.vue";
import {
  ICol,
  TOptionOfTools,
  IRowBtnsOptions,
  IToolsOptions,
  SubmitCallbacks,
  DeleteCallbacks,
  FyTableInstance,
} from "../types";
import { rawList2Tree, tree2RawList, deepCopy } from "../../helpers";

export default defineComponent({
  name: "FyTable",
  components: {
    TableTools,
  },
  // 禁用组件的根元素继承 attribute
  inheritAttrs: false,
  props: {
    /**
     * 表格列配置
     */
    cols: {
      type: Array as PropType<ICol[]>,
      required: true,
    },
    /**
     * 表格高度
     */
    height: {
      type: String,
      default: "600px",
    },
    /**
     * 显示分页
     */
    page: {
      type: Boolean,
      default: false,
    },
    /**
     * 页码：当前页
     */
    curr: {
      type: Number,
      default: 1,
    },
    /**
     * 数据总条数。一般通过服务端得到
     */
    total: {
      type: Number,
      default: 500,
    },
    /**
     * 每页条数的选择项，这个一般不用配置
     */
    limits: {
      type: Array as PropType<number[]>,
      // 对象或数组默认值必须从一个工厂函数获取
      default: () => [15, 30, 50, 100, 200],
    },
    loading: {
      type: Boolean,
      default: false,
    },
    /**
     * 显示边框
     */
    border: {
      type: Boolean,
      default: true,
    },
    /**
     * 数据
     */
    data: {
      type: Array,
      default: () => [],
    },
    /**
     * 表格行按钮组配置项
     */
    rowBtnsOptions: {
      type: Object as PropType<IRowBtnsOptions>,
    },
    /**
     * 工具栏配置项
     */
    toolsOptions: {
      type: Object as PropType<IToolsOptions>,
    },
  },
  emits: [
    "toolsClick",
    "searchSubmit",
    "searchReset",
    "toolsRefresh",
    "toolsDelete",
    "pageSizeChange",
    "pageCurrChange",
    "filterChange",
    "rowBtnsSubmit",
    "rowBtnsDelete",
    "toolsAddDialogSubmit",
    "update:cols",
    "update:toolsOptions",
    "update:rowBtnsOptions",
  ],
  setup(props, { emit }) {
    const { limits, height, page } = props;

    /* 表格对象 */
    const myTable: Ref<FyTableInstance | null> = ref(null);

    // 拷贝数据
    const copyData = ref<any[]>([]);

    // filter数据
    const filterData = ref<any[]>([]);

    watch(
      () => props.data,
      (val: any[]) => {
        if (val && Array.isArray(val)) {
          const list = deepCopy(val);
          // 缓存
          copyData.value = list;
          filterData.value = list;
        }
      },
      { immediate: true }
    );

    // 搜索表单插槽配置项
    const searchSlotOpts = computed(
      () =>
        props.toolsOptions?.searchFormOptions?.filter(
          (opt: { slot?: string }) => opt.slot
        ) || []
    );

    // 工具栏新增弹窗表单插槽
    const toolsAddDialogSlotOptions = computed(
      () =>
        props.toolsOptions?.addFormOptions?.filter(
          (opt: { slot?: string }) => opt.slot
        ) || []
    );
    // 行按钮编辑弹窗表单插槽
    const rowBtnsEditDialogSlotOptions = computed(
      () =>
        props.rowBtnsOptions?.editFormOptions?.filter(
          (opt: { slot?: string }) => opt.slot
        ) || []
    );
    // 行按钮新增弹窗表单插槽
    const rowBtnsAddDialogSlotOptions = computed(
      () =>
        props.rowBtnsOptions?.addFormOptions?.filter(
          (opt: { slot?: string }) => opt.slot
        ) || []
    );
    // 行按钮详情弹窗插槽
    const rowBtnsDetailDialogSlotOptions = computed(
      () =>
        props.rowBtnsOptions?.detailOptions?.filter(
          (opt: { slot?: string }) => opt.slot
        ) || []
    );

    /* 工具栏点击 */
    const handleToolsBtnClick = (btn: TOptionOfTools, args?: any) => {
      emit("toolsClick", btn, args);
    };

    /* 表格最大高度计算 */
    const tableBox = ref();
    const tableMaxHeight = computed(() =>
      tableBox.value ? tableBox.value.offsetHeight - (page ? 47 : 0) : height
    );

    /* 分页 */
    // 每页显示的条数。值需对应 limits 参数的选项。
    const limit = ref(limits[0]);

    // 每页条数变更
    const handleSizeChange = (val: number) => {
      limit.value = val;
      emit("pageSizeChange", val);
    };
    // 当前页变更
    const handleCurrChange = (val: number) => {
      emit("pageCurrChange", val);
    };

    // 监听多选
    const handleSelectionChange = (val: unknown[]) => {
      if (myTable.value) {
        myTable.value.selection = val;
      }
    };

    // 监听筛选filter
    const handleFilterChange = (newFilters: Record<string, unknown[]>) => {
      let list = tree2RawList({ treeList: deepCopy(copyData.value) });
      Object.keys(newFilters).forEach((key) => {
        if (newFilters[key].length) {
          list = list.filter((item: any) =>
            newFilters[key].includes(item[key])
          );
        }
      });
      filterData.value = rawList2Tree({ rawList: list });
      emit("filterChange", newFilters);
    };

    // row-btns事件
    const rowBtnsSubmit = (
      formData: Record<string, unknown>,
      callbacks: SubmitCallbacks, // 对象参数
      btn?: "edit" | "add"
    ) => {
      emit(
        "rowBtnsSubmit",
        formData,
        callbacks, // 对象参数
        btn
      );
    };

    // row-btns事件
    const rowBtnsDelete = (
      rowData: Record<string, unknown>,
      callbacks: DeleteCallbacks
    ) => {
      emit("rowBtnsDelete", rowData, callbacks);
    };

    // 工具栏新增弹窗Submit
    const handleToolsAddSubmit = (
      formData: Record<string, unknown>,
      callbacks: SubmitCallbacks
    ) => {
      emit("toolsAddDialogSubmit", formData, callbacks);
    };

    // 工具栏删除按钮
    const handleToolsDelete = (
      selection: FyTableInstance["selection"],
      callbacks: DeleteCallbacks
    ) => {
      emit("toolsDelete", selection, callbacks);
    };

    // 更新toolsOptions
    const updateToolsOptions = (option: IToolsOptions) => {
      emit("update:toolsOptions", { ...props.toolsOptions, ...option });
    };

    // 更新rowBtnsOptions
    const updateBtnsOptions = (option: IRowBtnsOptions) => {
      emit("update:rowBtnsOptions", { ...props.rowBtnsOptions, ...option });
    };

    return {
      copyData,
      filterData,
      handleToolsBtnClick,
      tableBox,
      tableMaxHeight,
      limit,
      handleSizeChange,
      handleCurrChange,
      myTable,
      tableRef: computed(() => myTable), // 这里使用computed对elTable进行跟踪，传递到tools子组件
      handleSelectionChange,
      handleFilterChange,
      rowBtnsSubmit,
      rowBtnsDelete,
      handleToolsAddSubmit,
      handleToolsDelete,
      toolsAddDialogSlotOptions,
      rowBtnsEditDialogSlotOptions,
      rowBtnsAddDialogSlotOptions,
      rowBtnsDetailDialogSlotOptions,
      searchSlotOpts,
      updateToolsOptions,
      updateBtnsOptions,
    };
  },
});
</script>

<style scoped lang="scss">
.table-page-nav {
  margin-top: 15px;
  display: flex;
  justify-content: flex-end;
}

:deep(th.el-table__cell) {
  background: #fafafa !important;

  .cell {
    color: var(--el-text-color-regular);
  }
}
.header-tip {
  display: flex;
  align-items: center;
  :deep(.fy-icon) {
    margin-top: -1px;
  }
}
</style>
<style lang="scss">
.el-table {
  --el-table-current-row-background-color: var(--el-color-primary-light-5);
  max-height: 100% !important;

  .cell {
    .el-input {
      line-height: 26px;
    }

    .el-input__inner {
      height: 26px;
      line-height: 26px;
    }

    .el-checkbox {
      height: 14px;
      transform: translate(-1px, 3px);
    }
  }
}

@media screen and (max-width: 1024px) {
  .table-page-nav {
    .el-pagination__total,
    .el-pagination__sizes,
    .el-pagination__jump {
      display: none !important;
    }
  }
}
</style>
